package com.tasks.server;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import org.apache.log4j.Logger;

import com.tasks.table.*;

public abstract class Table {

	static Logger logger = Logger.getLogger(Table.class);
	
	public static final boolean STRING_FORMAT 	= false;
	public static final boolean XML_FORMAT 		= true;
	
	private ArrayList<Column> row	= new ArrayList<Column>();
	private String className		= getClass().getSimpleName().toUpperCase();
	private Column primaryKey 		= new Column(className + "ID", 	Column.INTEGER);
	private Column dateCreated 		= new Column("DTCREATED", 		Column.DATETIME);
	private Column dateModified 	= new Column("DTMODIFIED", 		Column.DATETIME);
	private Column rowVer 			= new Column("ROWVER", 			Column.INTEGER);
	
	public Table()
	{
	}
	
	public Table(Server s, Executor exe, Column[] columns)
	{
		primaryKey.setData(exe.getInt(className + "ID"));
	  	for(int i = 0; i<columns.length; i++)
	  	{
	  		addColumn(s, exe, columns[i]);
	  	}		
		dateCreated.setData(exe.getDateTime("DTCREATED"));
		dateModified.setData(exe.getDateTime("DTMODIFIED"));
	  	rowVer.setData(exe.getInt("ROWVER"));
	}
		
	public void addColumn(Column c)
	{
		row.add(c);
	}
	
	public void addColumn(Server s, Executor exe, Column c)
	{
		if(c.getDataType() == Column.INTEGER) 
		{
			c.setData(exe.getInt(c.getColumnName()));
		}
		else if(c.getDataType() == Column.TEXT)
		{
			c.setData(exe.getString(c.getColumnName()));
		}
		else if(c.getDataType() == Column.DATETIME)
		{
			c.setData(exe.getDateTime(c.getColumnName()));
		}
		else if(c.getDataType() == Column.BOOLEAN)
		{
			c.setData(exe.getBoolean(c.getColumnName()));
		}
		else if(c.getDataType() == Column.ENUM)
		{
			c.setData(exe.getEnum(c.getColumnName()));
		}
		else if(c.getDataType() == Column.TABLE)
		{
			Class<?> classType = getTableClass(c.getColumnName());
			try
			{
				c.setData((Table) classType.getMethod("fetchById", new Class[]{Server.class, Integer.class}).invoke(null, new Object[]{s, new Integer(exe.getInt(c.getColumnName()))}));
			}
			catch (Exception e)
			{
				new ServerException(this, "addColumn", "Error invoking \"fetchById\" method of table " + classType.getCanonicalName());
			}
		}
		row.add(c);
	}
	
	public void addColumn(Column c, Object o)
	{
		c.setData(o);
		row.add(c);
	}
	
	public void setColumn(Column c, Object o)
	{
		c.setData(o);
	}
	
	public void set(String column, Object o)
	{
		Column c = getColumnByName(column.toUpperCase());
		if(c != null)
		{
			c.setData(o);	
		} 
		else 
		{
			new ServerException(this, "set", "Invalid column name specified");
		}
	}
	
	public Object get(String columnName)
	{
		Column c = getColumnByName(columnName.toUpperCase());
		if(c != null)
		{
			return c.getData();	
		}
		return null;
	}
	
	public Column getPrimaryKey(){
		return primaryKey;
	}
	
	private Column getColumnByName(String name)
	{
		Iterator<Column> itr = row.iterator();
		while (itr.hasNext()) 
		{
			Column c = itr.next();
			if(c.getColumnName().compareTo(name) == 0)
			{
				return c;
			}
		}		
		return null;
	}
	
	public static Class<?> getTableClass(String cls)
	{
		LinkedList<Object[]> classes = new LinkedList<Object[]>();
		classes.add(new Object[]{"USER", User.class});
		classes.add(new Object[]{"TASK", Task.class});
		
		for(Iterator<Object[]> i = classes.iterator(); i.hasNext();)
		{
			Object[] o = i.next();
			if(((String)o[0]).compareTo(cls) == 0)
			{
				return (Class<?>) o[1];
			}
		}		
		return null;
	}
	
	public final void save(Server server)
	{
		Integer rowVersion = (Integer)rowVer.getData();		
		String dateType = server.getDBType() == Server.MYSQL_DB ? "UTC_TIMESTAMP()" : "datetime('now')";
				
		if(rowVersion == null || rowVersion.intValue() == 0) 
		{
			create(server, dateType);
		} 
		else 
		{
			rowVer.setData((rowVersion.intValue() + 1));
			update(server, dateType);
		}
	}
	
	private void create(Server server, String dateType)
	{
		Executor executor = new Executor(server);
		
		String sql = "INSERT INTO " + className + "\n(\n  "; 
		for(int i=0; i<row.size(); i++)
		{
			sql += row.get(i).getColumnName() + ",\n  ";
		}
		sql += "DTCREATED,\n  DTMODIFIED,\n  ROWVER\n";
		sql += ")\nVALUES\n(\n  ";
		for(int i=0; i<row.size(); i++)
		{
			sql += row.get(i).toServer() + ",\n  ";
		}
		sql += dateType + ",\n  " + dateType + ",\n  '1'\n);";
		
		logger.debug("\n" + sql);
		
		primaryKey.setData(executor.executeInsert(sql));
		rowVer.setData(new Integer(1));
		
		executor.execute("SELECT " + dateType + " AS DT;");
		
		DateTime dt = executor.getDateTime("DT");
		dateCreated.setData(dt);
		dateModified.setData(dt);
		
		executor.close();
		executor = null;
	}
	
	private void update(Server server, String dateType)
	{
		Executor executor = new Executor(server);
		
		String sql = "UPDATE \t" + className + "\nSET ";
		for(int i=0; i<row.size(); i++)
		{
			sql += "\t" + row.get(i).getColumnName() + " = ";
			sql += row.get(i).toServer() + ",\n";
		}
		sql += "\t" + dateModified.getColumnName() + " = " + dateType;
		sql += ",\n\t" + rowVer.getColumnName() + " = " + rowVer.toServer();
		sql += "\nWHERE\t" + className + "ID = " + primaryKey.toServer() + ";";
		
		logger.debug("\n"+sql);
      
		executor.executeUpdate(sql);
		
		executor.execute("SELECT " + dateType + " AS DT;");
		dateModified.setData(executor.getDateTime("DT"));
		
		executor.close();
		executor = null;
	}
	
	public String toString(boolean format)
	{
		if(!format)
		{
			return toString(0);
		}
		else 
		{
			return toXML(0);
		}
	}
	
	public String toString()
	{
		return toString(XML_FORMAT);
	}
	
	
	private String toString(int depth)
	{
		String tabCar = "";
		for(int i=0; i<depth; i++)
		{
			tabCar += "\t";
		}		
		
		String retVal = tabCar + className + "\n";		
		retVal += tabCar + className + "ID: " + primaryKey.toString() + "\n";
		
		for(int i=0; i<row.size(); i++)
		{
			retVal += tabCar + row.get(i).getColumnName() + ": ";
			if(row.get(i).getDataType() == Column.TABLE)
			{
				retVal += ((Table)row.get(i).getData()).toString(depth+1); 
			}
			else 
			{
				retVal += row.get(i).toString() + "\n";
			}
		}
		
		retVal += tabCar + "DTCREATED: " + dateCreated.toString() + "\n";
		retVal += tabCar + "DTMODIFIED: " + dateModified.toString() + "\n";
		retVal += tabCar + "ROWVER: " + rowVer.toString() + "\n";
			
		return retVal;
	}
	
	private String toXML(int depth)
	{
		String out = "";
		String OutSpacer = "";
		String InSpacer = "  ";
		for(int i=0; i<depth; i++)
		{
			OutSpacer += InSpacer;
			InSpacer += InSpacer;
		}	
		
		out += OutSpacer + "<" + className + ">\n";	
		out += InSpacer + "<" + primaryKey.getColumnName() + ">";
		out += primaryKey.toString() + "</" + primaryKey.getColumnName() + ">\n";
		for(int i=0; i<row.size(); i++)
		{
			if(row.get(i).getDataType() == Column.TABLE)
			{
				out += ((Table)row.get(i).getData()).toXML(depth+1) + "\n";
			}
			else 
			{
				out += InSpacer + "<" + row.get(i).getColumnName() + ">";
				out += row.get(i).toString();
				out += "</" + row.get(i).getColumnName() + ">\n";
			}			
		}
		
		out += InSpacer + "<DTCREATED>" + dateCreated.toString() + "</DTCREATED>\n";
		out += InSpacer + "<DTMODIFIED>" + dateModified.toString() + "</DTMODIFIED>\n";
		out += InSpacer + "<ROWVER>" + rowVer.toString() + "</ROWVER>\n";
		out += OutSpacer + "</" + className + ">";
		return out;
	}
	
}
